import Router from 'koa-router'
import index from '../controllers/index'
import login from '../controllers/login'
import { BaseResponse, ResponseWithBody } from '../models/baseResponse'
import dBInstance from '../db/index'
import { RecordMode, LogStatus } from '../common/enums'
import { sendWebSocket } from '../common/sendWebSocket'
import { PNG } from 'pngjs'
import pixelmatch from 'pixelmatch'
import { Readable } from 'stream'

const router = Router()

router.get('/', index)
router.get('/login', login)

// SUITE
router.get('/api/getSuiteDetail', async (ctx, next) => {
    const result = await dBInstance.suiteDetails.getDetailsById(ctx.request.query.id)
    ctx.body = result
})

router.get('/api/getAllSuitesOfPage', async (ctx, next) => {
    const result = await dBInstance.suites.getAllSuitesByPage(ctx.request.query.page)
    ctx.body = result
})

router.get('/api/getAllSuitesOfPlan', async (ctx, next) => {
    const result = await dBInstance.plans.getAllSuitesOfPlan(ctx.request.query.planId)
    ctx.body = result
})

router.get('/api/getAllStepsOfSuiteInPlan', async (ctx, next) => {
    const { planId, suiteId } = ctx.request.query

    const result = await dBInstance.plans.getAllStepsOfSuiteInPlan({ planId, suiteId })
    ctx.body = result
})

router.get('/api/getAllSuites', async (ctx, next) => {
    const result = await dBInstance.suites.getAllSuites()
    ctx.body = result
})

// STEP COLLECTION
router.get('/api/getAllStepCollections', async (ctx, next) => {
    const result = await dBInstance.stepCollections.getAllStepCollections()
    ctx.body = result
})

router.post('/api/saveStepCollectionDetail', async (ctx, next) => {
    const stepCollectionMaster = ctx.request.body
    const stepCollectionDetails = ctx.request.body.list
    const stepCollectionId = stepCollectionMaster.id

    delete stepCollectionMaster.list

    const { mode: masterMode } = stepCollectionMaster
    delete stepCollectionMaster.mode
    delete stepCollectionMaster.id

    switch (masterMode) {
        case RecordMode.add:
            const insertResult = await dBInstance.stepCollections.insert(stepCollectionMaster)
            const addedStepCollectionId = insertResult.insertedId.toString()
            await dBInstance.stepCollectionDetails.insertMany(
                stepCollectionDetails.map(detail => dBInstance.stepCollectionDetails.genToAddItem({ ...detail, stepCollectionId: addedStepCollectionId })))
            break
        case RecordMode.edit:
            await dBInstance.stepCollections.updateById(stepCollectionId, stepCollectionMaster)
            await dBInstance.stepCollectionDetails.cleanAndSaveList(stepCollectionId, stepCollectionDetails)
            break
        default:
            break
    }

    ctx.body = new BaseResponse()
})

router.get('/api/getStepCollectionDetail', async (ctx, next) => {
    const result = await dBInstance.stepCollectionDetails.getDetailsById(ctx.request.query.id)
    ctx.body = result
})

router.post('/api/saveSuiteDetail', async (ctx, next) => {
    const suiteMaster = ctx.request.body
    const suiteDetails = ctx.request.body.list
    const suiteId = suiteMaster.id

    delete suiteMaster.list

    const { mode: masterMode } = suiteMaster
    delete suiteMaster.mode
    delete suiteMaster.id

    switch (masterMode) {
        case RecordMode.add:
            const insertResult = await dBInstance.suites.insert(suiteMaster)
            const addedSuiteId = insertResult.insertedId.toString()
            await dBInstance.suiteDetails.insertMany(
                suiteDetails.map(detail => dBInstance.suiteDetails.genToAddItem({ ...detail, suiteId: addedSuiteId })))
            break
        case RecordMode.edit:
            await dBInstance.suites.updateById(suiteId, suiteMaster)
            await dBInstance.suiteDetails.cleanAndSaveList(suiteId, suiteDetails)
            break
        default:
            break
    }

    ctx.body = new BaseResponse()
})

router.get('/api/deleteSuite', async (ctx, next) => {
    const suiteId = ctx.request.query.suiteId

    await dBInstance.suites.deleteById(suiteId)
    await dBInstance.suiteDetails.delete({ suiteId })

    ctx.body = new BaseResponse()
})

// LOGS
const getIfLogAssertImageEqualToBaseEnv = log => {
    return log
        && log.type === 'step'
        && log.step.code === 'assert'
        && log.step.data.assertTypeCode === 'equalToBaseEnv'
        && log.step.data.equalCompareType === 'image'
}

router.get('/api/statLogBySuite', async (ctx, next) => {
    const planId = ctx.request.query.planId
    const allSuites = await dBInstance.suites.find({ planId })
    const detailStats = await dBInstance.suiteDetails.statDetailCount()
    const finished = await dBInstance.logs.statLogBySuite()

    const finalMap = {}
    detailStats.forEach(detail => {
        const { id: suiteId, count: total } = detail

        if (!finalMap[suiteId]) {
            finalMap[suiteId] = { total: 0, finished: 0 }
        }

        const log = finalMap[suiteId]
        log.total = total
        log.finished = (finished.find(item => item.id === suiteId) || { count: 0 }).count
        log.page = (allSuites.find(item => item.id === suiteId) || { page: '' }).page
    })


    ctx.body = finalMap
})

router.get('/api/statLogsOfPlan', async (ctx, next) => {
    const planId = ctx.request.query.planId
    const allLogsOfPlan = await dBInstance.logs.statLogsOfPlan(planId)
    const totalCountOfSuitesInPlan = await dBInstance.plans.getAllStepCountOfPlan({ planId })

    const final = Object.keys(totalCountOfSuitesInPlan).reduce((result, suiteId) => {
        result[suiteId] = {
            total: totalCountOfSuitesInPlan[suiteId],
            ...allLogsOfPlan[suiteId]
        }
        return result
    }, {})

    ctx.body = final
})

router.get('/api/getStepFullLog', async (ctx, next) => {
    const planId = ctx.request.query.planId
    const stepId = ctx.request.query.stepId
    // logs of the step
    const logs = await dBInstance.logs.find({ planId, stepId })
    // final status step log
    const matchedLog = logs.find(log => log.status !== LogStatus.start && log.status !== LogStatus.pending)

    // append
    const isAssertEqualToBaseEnv = getIfLogAssertImageEqualToBaseEnv(matchedLog)
    if (isAssertEqualToBaseEnv) {
        const { expected, actual, valid } = matchedLog.result
        matchedLog.result.difference = ''

        if (expected && actual && !valid) {
            const comparePromise = new Promise(resolve => {
                const parseImage = base64 => {
                    const expectedStream = new Readable()
                    expectedStream.push(Buffer.from(base64, 'base64'))
                    expectedStream.push(null)
                    const img = expectedStream.pipe(new PNG()).on('parsed', compare)

                    return img
                }

                const expectedImg = parseImage(expected)
                const actualImg = parseImage(actual)

                let filesRead = 0

                function compare() {
                    if (++filesRead < 2) return
                    const width = Math.max(expectedImg.width, actualImg.width)
                    const height = Math.max(expectedImg.height, actualImg.height)
                    let diffPng = new PNG({ width, height })

                    const differentPixelCount = pixelmatch(expectedImg.data, actualImg.data, diffPng.data, width, height, { threshold: 0.1 });

                    if (differentPixelCount > 0) {
                        diffPng.pack()
                        let chunks = []
                        diffPng.on('data', function (chunk) {
                            chunks.push(chunk)
                        })

                        diffPng.on('end', function () {
                            let result = Buffer.concat(chunks)
                            const diffBase64 = result.toString('base64')

                            resolve(diffBase64)
                        })
                    } else {
                        resolve('')
                    }
                }
            })

            const diffBase64 = await comparePromise
            matchedLog.result.difference = diffBase64
        }
    }

    ctx.body = matchedLog
})

router.get('/api/getLogsOfSuite', async (ctx, next) => {
    const planId = ctx.request.query.planId
    const suiteId = ctx.request.query.suiteId
    // logs of the step
    const logs = await dBInstance.logs.find({ planId, suiteId })

    ctx.body = logs
})

router.get('/api/updateLogRemark', async (ctx, next) => {
    const { id, remark } = ctx.request.query
    const result = await dBInstance.logs.updateRemark(id, remark)

    ctx.body = result
})

// SCREENSHOT
router.get('/api/getScreenShotOfStep', async (ctx, next) => {
    const { planId, stepId, screenshotId } = ctx.request.query
    const result = await dBInstance.screenshots.getByPlanStepId({ planId, stepId, screenshotId })
    ctx.body = result
})

// ELEMENT
router.get('/api/getAllElementsOfPage', async (ctx, next) => {
    const result = await dBInstance.elements.getAllByPage(ctx.request.query.page)
    ctx.body = result
})

router.get('/api/getElementsOfIds', async (ctx, next) => {
    const result = await dBInstance.elements.findByIds(ctx.request.query.ids.split(','))
    ctx.body = result
})

router.post('/api/saveElements', async (ctx, next) => {
    await dBInstance.elements.saveList(ctx.request.body.list)
    ctx.body = new BaseResponse()
})

// AJAX
router.get('/api/getAllAjaxsOfPage', async (ctx, next) => {
    const result = await dBInstance.ajaxs.getAllByPage(ctx.request.query.page)
    ctx.body = result
})

router.get('/api/getAjaxsOfIds', async (ctx, next) => {
    const result = await dBInstance.ajaxs.findByIds(ctx.request.query.ids.split(','))
    ctx.body = result
})

router.post('/api/saveAjaxs', async (ctx, next) => {
    await dBInstance.ajaxs.saveList(ctx.request.body.list)
    ctx.body = new BaseResponse()
})

// MOCK
router.get('/api/getAllMocksOfAjax', async (ctx, next) => {
    const result = await dBInstance.mocks.getAllByAjax(ctx.request.query.id)
    ctx.body = result
})

router.get('/api/getMocksOfIds', async (ctx, next) => {
    const result = await dBInstance.mocks.findByIds(ctx.request.query.ids.split(','))
    ctx.body = result
})

router.post('/api/saveMocks', async (ctx, next) => {
    await dBInstance.mocks.saveList(ctx.request.body.list)
    ctx.body = new BaseResponse()
})

// CONFIG
router.get('/api/getAllConfigs', async (ctx, next) => {
    const result = await dBInstance.configs.getAll()
    ctx.body = result
})

router.post('/api/saveConfig', async (ctx, next) => {
    await dBInstance.configs.save(ctx.request.body.configType, JSON.parse(ctx.request.body.configValue))
    ctx.body = new BaseResponse()
})

// PLAN
router.get('/api/getAllPlans', async (ctx, next) => {
    const result = await dBInstance.plans.find({}, { sort: { create_at: -1 }, limit: 1 }, { suites: 0 })
    ctx.body = result
})

router.get('/api/getAllPlanSummaries', async (ctx, next) => {
    const result = await dBInstance.plans.getAllPlanSummaries()
    ctx.body = result
})

router.post('/api/addPlan', async (ctx, next) => {
    const plan = await dBInstance.plans.insert(ctx.request.body.plan)
    sendWebSocket(['add_plan', {}])
    ctx.body = new ResponseWithBody({ id: plan.insertedId.toString() }, 200, '')
})

router.get('/api/clearData', async (ctx, next) => {
    const result = await dBInstance[ctx.request.query.collection].delete({})
    ctx.body = result
})

export default router
